---
title: 模板和泛型程式設計
---

模板是 C++ 中一個強大的功能，它允許你編寫適用於不同資料類型的通用程式碼，而無需為每種類型重寫相同的程式碼。這是一種**泛型程式設計**的形式，旨在編寫獨立於其操作的特定資料類型的演算法和資料結構。雖然 JavaScript 透過其動態類型實現了一種泛型形式，但 C++ 模板提供了編譯時類型安全和性能。

## 函式模板

**函式模板**定義了一系列可以操作不同資料類型的函式。編譯器根據呼叫模板時使用的類型生成實際的函式（模板實例化）。

<UniversalEditor title="函式模板比較" compare={true}>
```javascript !! js
// JavaScript: 通用函式 (動態類型處理泛型)
function identity(value) {
  return value;
}

console.log(identity(123)); // 123
console.log(identity("hello")); // "hello"
console.log(identity(true)); // true

function add(a, b) {
  return a + b;
}

console.log(add(5, 3)); // 8
console.log(add("Hello ", "World")); // "Hello World"
```

```cpp !! cpp
// C++: 函式模板
#include <iostream>
#include <string>

// 函式模板用於 identity
template <typename T>
T identity(T value) {
  return value;
}

// 函式模板用於 add
template <typename T>
T add(T a, T b) {
  return a + b;
}

int main() {
  // std::cout << identity(123) << std::endl; // T 變為 int
  // std::cout << identity("hello") << std::endl; // T 變為 const char*
  // std::cout << identity(true) << std::endl; // T 變為 bool

  // std::cout << add(5, 3) << std::endl; // T 變為 int
  // std::cout << add(5.5, 3.2) << std::endl; // T 變為 double
  // std::cout << add(std::string("Hello "), std::string("World")) << std::endl; // T 變為 std::string

  return 0;
}
```
</UniversalEditor>

## 類別模板

**類別模板**定義了一系列可以儲存和操作不同資料類型物件的類別。這通常用於通用資料結構，如列表、堆疊、佇列等。

<UniversalEditor title="類別模板比較" compare={true}>
```javascript !! js
// JavaScript: 通用類別 (動態類型)
class Box {
  constructor(value) {
    this.value = value;
  }

  getValue() {
    return this.value;
  }
}

const intBox = new Box(10);
console.log(intBox.getValue()); // 10

const stringBox = new Box("text");
console.log(stringBox.getValue()); // "text"
```

```cpp !! cpp
// C++: 類別模板
#include <iostream>
#include <string>

template <typename T>
class Box {
public:
  T value;

  Box(T val) : value(val) {}

  T getValue() {
    return value;
  }
};

int main() {
  Box<int> intBox(10); // 使用 int 實例化 Box
  // std::cout << intBox.getValue() << std::endl; // 10

  Box<std::string> stringBox("text"); // 使用 std::string 實例化 Box
  // std::cout << stringBox.getValue() << std::endl; // "text"

  return 0;
}
```
</UniversalEditor>

## 模板特化和部分特化

有時，通用模板對於某些特定類型可能不是最佳的，甚至是不正確的。**模板特化**允許你為特定類型提供完全不同的實作。**部分特化**允許你為模板參數的子集提供特化實作。

### 完全特化

<UniversalEditor title="完全模板特化" compare={true}>
```javascript !! js
// JavaScript: 透過條件邏輯或類型檢查實現
function processData(data) {
  if (typeof data === 'string') {
    return `處理字串: ${data.toUpperCase()}`;
  } else if (typeof data === 'number') {
    return `處理數字: ${data * 2}`;
  } else {
    return `處理通用: ${data}`;
  }
}

console.log(processData("hello"));
console.log(processData(10));
```

```cpp !! cpp
// C++: 完全模板特化
#include <iostream>
#include <string>

template <typename T>
void printValue(T value) {
  // std::cout << "通用列印: " << value << std::endl;
}

// int 的完全特化
template <>
void printValue<int>(int value) {
  // std::cout << "int 的特化列印: " << value * 2 << std::endl;
}

// std::string 的完全特化
template <>
void printValue<std::string>(std::string value) {
  // std::cout << "std::string 的特化列印: " << value << " (長度: " << value.length() << ")" << std::endl;
}

int main() {
  // printValue(5); // 呼叫 int 特化版本
  // printValue(3.14); // 呼叫通用 double 版本
  // printValue(std::string("test")); // 呼叫 std::string 特化版本
  return 0;
}
```
</UniversalEditor>

### 部分特化 (用於類別模板)

部分特化僅適用於類別模板，不適用於函式模板。

<UniversalEditor title="部分模板特化" compare={true}>
```javascript !! js
// JavaScript: 沒有直接等效，但可以透過不同實作實現類似邏輯
// 這是由 JavaScript 的動態特性處理的。
```

```cpp !! cpp
// C++: 部分模板特化 (用於類別模板)
#include <iostream>

template <typename T1, typename T2>
class Pair {
public:
  Pair(T1 v1, T2 v2) {
    // std::cout << "通用 Pair 已建立。" << std::endl;
  }
};

// Pair<T, int> 的部分特化
template <typename T>
class Pair<T, int> {
public:
  Pair(T v1, int v2) {
    // std::cout << "部分特化 Pair<T, int> 已建立。" << std::endl;
  }
};

int main() {
  // Pair<double, char> p1(1.1, 'a'); // 呼叫通用 Pair
  // Pair<double, int> p2(2.2, 10); // 呼叫部分特化 Pair<T, int>
  return 0;
}
```
</UniversalEditor>

## 可變參數模板

**可變參數模板**允許函式或類別接受任意數量的不同類型參數。這對於像 `printf` 這樣的函式或建立自訂日誌機制非常有用。

<UniversalEditor title="可變參數模板比較" compare={true}>
```javascript !! js
// JavaScript: 剩餘參數處理可變參數
function log(...args) {
  console.log(...args);
}

log("Hello", 123, true);
log("Only one argument");
```

```cpp !! cpp
// C++: 可變參數模板
#include <iostream>

// 遞迴的基礎情況
void log() {
  // std::cout << std::endl;
}

// 遞迴可變參數模板函式
template <typename T, typename... Args>
void log(T firstArg, Args... args) {
  // std::cout << firstArg << " ";
  log(args...); // 遞迴呼叫
}

int main() {
  // log("Hello", 123, true);
  // log("Only one argument");
  return 0;
}
```
</UniversalEditor>

## 模板元程式設計基礎

**模板元程式設計 (TMP)** 是一種技術，其中模板用於在編譯時而不是運行時執行計算。這可以產生高度優化的程式碼，但它也可能複雜且難以偵錯。

常見用途包括：
*   編譯時計算（例如，階乘、費波那契數列）。
*   類型特性（查詢類型的屬性）。
*   根據類型生成程式碼。

<UniversalEditor title="模板元程式設計範例" compare={true}>
```javascript !! js
// JavaScript: 編譯時計算不是原生概念。
// 所有計算都是運行時的。
function factorial(n) {
  if (n === 0) return 1;
  return n * factorial(n - 1);
}

console.log(factorial(5)); // 120
```

```cpp !! cpp
// C++: 模板元程式設計 (編譯時階乘)
#include <iostream>

template <int N>
struct Factorial {
  static const int value = N * Factorial<N - 1>::value;
};

template <>
struct Factorial<0> {
  static const int value = 1;
};

int main() {
  // 編譯時計算
  // std::cout << "5 的階乘: " << Factorial<5>::value << std::endl; // 120
  return 0;
}
```
</UniversalEditor>

## 與 JavaScript 泛型的比較

JavaScript 沒有像 C++ 模板那樣的正式「泛型」系統。它的動態類型系統本身提供了一種泛型形式：

*   **動態類型：** 函式和類別可以操作任何類型的值，而無需明確的類型參數。類型檢查在運行時進行。
*   **靈活性：** 這提供了極大的靈活性並減少了樣板程式碼。
*   **運行時錯誤：** 然而，與類型相關的錯誤在運行時捕獲，而不是編譯時。

相比之下，C++ 模板提供：
*   **編譯時泛型：** 類型在編譯時檢查，從而更早地偵測到錯誤並提高性能。
*   **類型安全：** 確保操作對於正在使用的類型是有效的。
*   **程式碼膨脹（潛在）：** 每個模板實例化都會生成單獨的程式碼，這可能會增加可執行檔的大小。

## 模板最佳實踐

1.  **保持簡單：** 當需要真正的泛型時才使用模板；避免過度設計。
2.  **分離宣告和定義（對於較大的模板）：** 對於類別模板，通常將宣告放在 `.h` 檔案中，將定義放在 `.tpp` 或 `.hpp` 檔案中，然後在 `.h` 檔案的末尾包含 `.tpp`。對於函式模板，定義通常在標頭中。
3.  **使用 `typename` vs. `class`：** `typename` 通常更適合模板類型參數，尤其是在處理依賴類型時。
4.  **基於概念的約束 (C++20)：** 使用 C++20 Concepts 來約束模板參數，使模板錯誤更具可讀性並提供更好的編譯時檢查。
5.  **避免過度元程式設計：** 雖然功能強大，但 TMP 可能會使程式碼難以閱讀和偵錯。謹慎使用。

---

### 練習題：
1.  解釋 C++ 中函式模板和類別模板的用途。提供一個簡單的範例。
2.  何時會使用模板特化？描述一個有益的場景。
3.  C++ 的模板系統與 JavaScript 實現泛型的方式有何不同？討論每種方法的優缺點。

### 專案構想：
*   在 C++ 中實作一個通用的 `Stack` 類別模板，它可以儲存任何資料類型的元素。包括 `push`、`pop`、`top` 和 `isEmpty` 等方法。演示其與 `int`、`double` 和 `std::string` 類型的用法。
